package chances.epg.data.ibatis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * 
 * @author xdwang
 * 
 * @ceate 2012-12-19 下午8:01:31
 * 
 * @description 然后就是实现mybatis提供的拦截器接口，编写我们自己的分页实现，原理就是拦截底层JDBC操作相关的Statement对象，
 *              把前端的分页参数如当前记录索引和每页大小通过拦截器注入到sql语句中
 *              ，即在sql执行之前通过分页参数重新生成分页sql,而具体的分页sql实现是分离到Dialect接口中去。
 * 
 * 
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class ,Integer.class}) })
public class PaginationInterceptor implements Interceptor {

	private final static Log log = LogFactory
			.getLog(PaginationInterceptor.class);
	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
	Dialect.Type databaseType = null;
	Dialect dialect = null;

	public PaginationInterceptor() {
		dialect = new MySQLDialect();
	}

	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler) invocation
				.getTarget();
		BoundSql boundSql = statementHandler.getBoundSql();
		Object parameterObject = boundSql.getParameterObject();
		MetaObject metaStatementHandler = MetaObject.forObject(
				statementHandler, DEFAULT_OBJECT_FACTORY,
				DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
		RowBounds rowBounds = (RowBounds) metaStatementHandler
				.getValue("delegate.rowBounds");
		if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
			return invocation.proceed();
		}

		/*
		 * Configuration configuration = (Configuration) metaStatementHandler
		 * .getValue("delegate.configuration"); try { databaseType =
		 * Dialect.Type.valueOf(configuration.getVariables()
		 * .getProperty("dialect").toUpperCase()); } catch (Exception e) { //
		 * ignore } if (databaseType == null) { throw new RuntimeException(
		 * "the value of the dialect property in configuration.xml is not defined : "
		 * + configuration.getVariables().getProperty( "dialect")); }
		 */

		String originalSql = (String) metaStatementHandler
				.getValue("delegate.boundSql.sql");
		Connection connection = (Connection) invocation.getArgs()[0];
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
				.getValue("delegate.mappedStatement");
		
		long count = getTotalCount(connection,  mappedStatement, boundSql,
				parameterObject);

		int begin = 0;
		if (rowBounds instanceof PageRowBounds) {
			PageRowBounds pager = (PageRowBounds) rowBounds;
			pager.setRecordCount(count - pager.getStart());
			begin = pager.getPageOffset();
		}
		
		//int begin = rowBounds.getOffset();
		String pageSql = dialect.getPageSql(originalSql, begin,
				rowBounds.getLimit());
		metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
		metaStatementHandler.setValue("delegate.rowBounds.offset",
				begin);
		metaStatementHandler.setValue("delegate.rowBounds.limit",
				RowBounds.NO_ROW_LIMIT);

		if (log.isDebugEnabled()) {
			log.debug("生成分页SQL : " + boundSql.getSql());
		}
		
		return invocation.proceed();

	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
		String str = properties.getProperty("dialect");
		if (StringUtils.isNotEmpty(str)) {
			this.databaseType = Dialect.Type.valueOf(str.toUpperCase());
			switch (databaseType) {
			case MYSQL:
				dialect = new MySQLDialect();
				break;
			case MSSQL:
				dialect = new SQLServerDialect();
				break;
			case ORACLE:
				dialect = new OracleDialect();
				break;
			default:
				dialect = new MySQLDialect();
			}
		}
	}

	/**
	 * get total count
	 * 
	 * @param sqlSession
	 * @param statementName
	 * @param values
	 * @return
	 */
	private long getTotalCount(Connection connection,
			MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) {
		long count = 0l;
		try {
			String sql = dialect.getCountSql(boundSql.getSql());
			PreparedStatement pstmt = connection.prepareStatement(sql);
			DefaultParameterHandler handler = new DefaultParameterHandler(
					mappedStatement, parameterObject, boundSql);
			handler.setParameters(pstmt);
			ResultSet rs = pstmt.executeQuery();
			if (rs.next()) {
				count = rs.getLong(1);
			}
			rs.close();
			pstmt.close();
		} catch (Exception e) {
			count = 0l;
			e.printStackTrace();
			throw new RuntimeException(e);
		}

		return count;
	}

}
